//
//  Helper.cpp
//  TRKUTIL
//
//  Created by 李通洲 on 15/2/2.
//  Copyright (c) 2015年 Wicresoft. All rights reserved.
//

#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <string>

#ifdef _WIN32
#   define WIN32_LEAN_AND_MEAN
#   include <Windows.h>
#else
#   include <sys/time.h>
#   include <sys/stat.h>
#   if defined(__APPLE__)
#       include <mach-o/dyld.h>
#   else
#       include <unistd.h>
#   endif
#endif

#include "Helper.hpp"

static std::string GetCurrentExecutablePathImpl() {
    // http://stackoverflow.com/questions/1023306/finding-current-executables-path-without-proc-self-exe

    std::string result(128, '\0');
#ifdef _WIN32
    std::size_t bufsize;
    while ((bufsize = ::GetModuleFileNameA(NULL, &result[0], result.length())) == result.length()) {
        result.resize(result.length() * 2);
    }
    result.resize(bufsize);
#elif defined(__APPLE__)
    uint32_t bufsize = static_cast<uint32_t>(result.length());
    if (::_NSGetExecutablePath(&result[0], &bufsize) != 0) {
        result.resize(bufsize);
        ::_NSGetExecutablePath(&result[0], &bufsize);
    }
#else
    ssize_t bufsize;
    while ((bufsize = ::readlink(
#   ifdef __linux__
        "/proc/self/exe"
#   else
        "/proc/curproc/file"
#   endif
        , &result[0], result.length())) >= static_cast<ssize_t>(result.length())) {
        result.resize(result.length() * 2);
    }
    if (bufsize < 0) {
        result.clear();
    } else {
        result.resize(bufsize);
    }
#endif

    result.resize(result.rfind(
#ifdef _WIN32
        '\\'
#else
        '/'
#endif
        ) + 1);
    return result;
}

const std::string& GetCurrentExecutablePath() {
    // Non-threadsafe before C++11
    // It's ok because we don't use multi-threading at all
    const static std::string cache = GetCurrentExecutablePathImpl();
    return cache;
}

// http://stackoverflow.com/questions/17432502/how-can-i-measure-cpu-time-and-wall-clock-time-on-both-linux-windows
//  Windows
#ifdef _WIN32
double get_wall_time() {
    LARGE_INTEGER time, freq;
    if (!QueryPerformanceFrequency(&freq)) {
        //  Handle error
        return 0;
    }
    if (!QueryPerformanceCounter(&time)) {
        //  Handle error
        return 0;
    }
    return (double) time.QuadPart / freq.QuadPart;
}
double get_cpu_time() {
    FILETIME a, b, c, d;
    if (GetProcessTimes(GetCurrentProcess(), &a, &b, &c, &d) != 0) {
        //  Returns total user time.
        //  Can be tweaked to include kernel times as well.
        return
            (double) (d.dwLowDateTime |
            ((unsigned long long)d.dwHighDateTime << 32)) * 0.0000001;
    } else {
        //  Handle error
        return 0;
    }
}

//  Posix/Linux
#else
double get_wall_time() {
    struct timeval time;
    if (gettimeofday(&time, NULL)) {
        //  Handle error
        return 0;
    }
    return (double) time.tv_sec + (double) time.tv_usec * .000001;
}
double get_cpu_time() {
    return (double) clock() / CLOCKS_PER_SEC;
}
#endif

bool CreateDirectoryIfNonexist(fmt::StringRef path) {
    // http://stackoverflow.com/questions/5621944/how-to-find-out-if-a-folder-exists-and-how-to-create-a-folder#answer-5622002

#ifdef _WIN32
    return CreateDirectoryA(path.c_str(), NULL) || ERROR_ALREADY_EXISTS == GetLastError();
#else
    return mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0 || errno == EEXIST;
#endif
}
